#include <avr/io.h>
#include <avr/cpufunc.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#include "Common.h"
#include "tests.h"
#include "Serial.h"

uint8_t const hexmap[] = {
	// 0
	0b00111100,
	0b01000010,
	0b01000010,
	0b01000010,
	0b01000010,
	0b01000010,
	0b00111100,
	
	// 1
	0b00011000,
	0b00111000,
	0b00011000,
	0b00011000,
	0b00011000,
	0b00011000,
	0b01111110,
	
	// 2
	0b00111100,
	0b01000010,
	0b00000100,
	0b00001000,
	0b00010000,
	0b00100000,
	0b01111110,
	
	// 3
	0b00111100,
	0b01000010,
	0b00000100,
	0b00011100,
	0b00000100,
	0b01000010,
	0b00111100,
	
	// 4
	0b00001100,
	0b00010100,
	0b00100100,
	0b01000100,
	0b01111110,
	0b00000100,
	0b00001110,
	
	// 5
	0b01111110,
	0b01000000,
	0b01000000,
	0b01111100,
	0b00000010,
	0b01000010,
	0b00111100,
	
	// 6
	0b00111100,
	0b01000000,
	0b01000000,
	0b01111100,
	0b01000010,
	0b01000010,
	0b00111100,
	
	// 7
	0b01111110,
	0b00000010,
	0b00000100,
	0b00001000,
	0b00010000,
	0b00010000,
	0b00010000,
	
	// 8
	0b00111100,
	0b01000010,
	0b01000010,
	0b00111100,
	0b01000010,
	0b01000010,
	0b00111100,
	
	// 9
	0b00111100,
	0b01000010,
	0b01000010,
	0b00111110,
	0b00000010,
	0b00000010,
	0b00111100,
	
	// A
	0b00011000,
	0b00100100,
	0b01000010,
	0b01111110,
	0b01000010,
	0b01000010,
	0b01000010,
	
	// B
	0b01111100,
	0b01000010,
	0b01000010,
	0b01111100,
	0b01000010,
	0b01000010,
	0b01111100,
	
	// C
	0b00111100,
	0b01000010,
	0b01000000,
	0b01000000,
	0b01000000,
	0b01000010,
	0b00111100,
	
	// D
	0b01111100,
	0b01000010,
	0b01000010,
	0b01000010,
	0b01000010,
	0b01000010,
	0b01111100,
	
	// E
	0b01111110,
	0b01000000,
	0b01000000,
	0b01111000,
	0b01000000,
	0b01000000,
	0b01111110,
	
	// F
	0b01111110,
	0b01000000,
	0b01000000,
	0b01111100,
	0b01000000,
	0b01000000,
	0b01000000,
};


enum mode_m { TEXT = 4, GRAPHICS1 = 0, GRAPHICS2 = 1, MULTICOLOR = 2 };
typedef enum mode_m mode_m;

uint8_t mode0 = 0b00000000;
uint8_t mode1 = 0b10000000;
uint8_t mode7 = 0x00;

/**
* \brief Sets VDC mode
*
* \param mode supported VDC mode number
*
* \return void
*/
static void setVDCMode( mode_m mode )
{
	mode0 = (mode & 1) << 1;
	mode1 = (mode1 & 0b11100111) | ((mode & 0b110) << 2);
	writeVDCReg( 0, mode0 );
	writeVDCReg( 1, mode1 );
}

/**
* \brief Turns VDC display on or off according to flag
*
* \param visible True to turn on
*
* \return void
*/
static void setVDCDisplay( bool visible )
{
	mode1 = (mode1 & 0b10111111) | ((visible) ? 0b01000000 : 0b00000000);
	writeVDCReg( 1, mode1 );
}

/**
* \brief Enables or disables VDC interrupt on vertical retrace
*
* \param enabled True to enable interrupts
*
* \return void
*/
static void setVDCIntEnable( bool enabled )
{
	mode1 = (mode1 & 0b11011111) | ((enabled) ? 0b00100000 : 0b00000000);
	writeVDCReg( 1, mode1 );
}

/**
* \brief Sets the size of on-screen sprites
*
* \param sz 1 for 2x2 sprites, 0 for 1x1
*
* \return void
*/
static void setVDCSpriteSize( uint8_t sz )
{
	mode1 = (mode1 & 0b11111101) | ((sz == 1) ? 0b00000010 : 0b00000000);
	writeVDCReg( 1, mode1 );
}

/**
* \brief Sets VDC magnification
*
* \param mag 1 for 2x magnification, 0 for 1x
*
* \return void
*/
static void setVDCMag( uint8_t mag )
{
	mode1 = (mode1 & 0b11111110) | ((mag == 1)  ? 0b00000001 : 0b00000000);
	writeVDCReg( 1, mode1 );
}

/**
* \brief Sets all mode bits at once
*
* \param mode Supported display mode
* \param visible True to turn display on
* \param intenable True to enable vertical retrace interrupts
* \param spritesize 1 for 2x2 sprite size, 0 for 1x1
* \param magsize 1 for 2x magnification, 0 for 1x
*
* \return void
*/
static void setVDCModeAll( mode_m mode, bool visible, bool intenable, uint8_t spritesize, uint8_t magsize )
{
	mode0 = (mode & 1) << 1;
	mode1 = 0b10000000 |
	((mode & 0b110) << 2) |
	((visible) ? 0b01000000 : 0b00000000) |
	((intenable) ? 0b00100000 : 0b00000000) |
	((spritesize == 1) ? 0b00000010 : 0b00000000) |
	((magsize == 1)  ? 0b00000001 : 0b00000000);

	writeVDCReg( 0, mode0 );
	writeVDCReg( 1, mode1 );
}

/**
* \brief Sets the address of the name table
*
* \param addr Address in 0x400 multiples
*
* \return void
*/
static void setVDCNameTableAddr( uint16_t addr )
{
	addr >>= 10;
	writeVDCReg( 2, (uint8_t) addr );
}

/**
* \brief Sets the address of the pattern color table
*
* \param addr Address in 0x40 multiples
*
* \return void
*/
static void setVDCColorTableAddr( uint16_t addr )
{
	addr >>= 6;
	if( mode0 & 0b00000010 )
	{
		addr |= 0b01111111;
	}
	writeVDCReg( 3, (uint8_t) addr );
}

/**
* \brief Sets the address of the pattern bitmap table
*
* \param addr Address in 0x800 multiples
*
* \return void
*/
static void setVDCPatternTableAddr( uint16_t addr )
{
	addr >>= 11;
	if( mode0 & 0b00000010 )
	{
		addr |= 0b00000011;
	}
	writeVDCReg( 4, (uint8_t) addr );
}

/**
* \brief Set the address of the sprite attribute table
*
* \param addr Address in 0x80 multiples
*
* \return void
*/
static void setVDCSpriteAttrTableAddr( uint16_t addr )
{
	addr >>= 7;
	writeVDCReg( 5, (uint8_t) addr );
}

/**
* \brief Set the sprite bitmap table address
*
* \param addr Address in 0x800 multiples
*
* \return void
*/
static void setVDCSpriteTableAddr( uint16_t addr )
{
	addr >>= 11;
	writeVDCReg( 6, (uint8_t) addr );
}

/**
* \brief Set the text foreground color in TEXT mode
*
* \param color New foreground color
*
* \return void
*/
static void setVDCFGColor( uint8_t color )
{
	mode7 = (mode7 & 0x0f) | (color << 4);
	writeVDCReg( 7, mode7 );
}

/**
* \brief Set the background color
*
* \param color New background color
*
* \return void
*/
static void setVDCBGColor( uint8_t color )
{
	mode7 = (mode7 & 0xf0) | (color & 0x0f);
	writeVDCReg( 7, mode7 );
}

/**
* \brief Set the colors used for text foreground and background
*
* \param fgcolor  Text foreground color for TEXT mode
* \param bgcolor  New background color
*
* \return void
*/
static void setVDCColors( uint8_t fgcolor, uint8_t bgcolor )
{
	mode7 = (fgcolor << 4) | (bgcolor & 0x0f);
	writeVDCReg( 7, mode7 );
}

#define SYNCF(x) { TIFR1 = (1 << OCF1##x); while( (TIFR1 & (1 << OCF1##x)) == 0 ); }
#define SYNCH(x) { TIFR4 = (1 << OCF4##x); while( (TIFR4 & (1 << OCF4##x)) == 0 ); }

bool doExtVideoTestNTSC()
{
	// Disable the CV CPU
	doBusAck();
	
	// Place in external video mode
	DDRF = 0b10011111;
	PORTF = 0b10010000;

	// Hold the peripheral clock
	DDRG |= 0b00000010;
	PORTG &= 0b11111101;

	// Hold VDC reset
	DDRJ |= 0b01000000;
	PORTJ &= 0b10111111;

	int     i;
	uint8_t j;
	uint8_t imask;

	// NTSC line is 63.492us, or 1015.9 clock cycles at 16MHz
	// Field-blanking lines are half-width
	#define  LINE_WIDTH 1016
	#define  PRE_EQ_PULSE 36
	#define  PRE_EQ_GAP   ((LINE_WIDTH/2) - PRE_EQ_PULSE)
	#define  FIELD_SYNC_PULSE 433
	#define  FIELD_SYNC_GAP ((LINE_WIDTH/2) - FIELD_SYNC_PULSE)
	#define  FRONT_PORCH 24
	#define  SYNC_PULSE 75
	#define  BACK_PORCH (SYNC_PULSE + 72)
	#define  NUM_PIXELS 90
	#define  VIDEO_LINES 262

	delay( 100 );
	while( serial_available_f(stdin) ) { getchar(); delay( 100 ); }

	GTCCR = 0b10000001;

	// Set up the timers for consistent frames
	TCCR1A = 0b00000000; // CTC mode, OCR1A
	TCCR1B = 0b00001001;
	TCCR1C = 0b00000000;
	OCR1A = LINE_WIDTH - 1;
	OCR1B = SYNC_PULSE - 1;
	OCR1C = BACK_PORCH - 1;
	
	TCCR4A = 0b00000000; // CTC mode, OCR4A
	TCCR4B = 0b00001001;
	TCCR4C = 0b00000000;
	OCR4A = LINE_WIDTH / 2 - 1;
	OCR4B = PRE_EQ_PULSE - 1;
	OCR4C = FIELD_SYNC_PULSE - 1;
	
	TCNT1 = 0;
	TCNT4 = 0;

	GTCCR = 0b00000000;
	
	// Also set up the timer to output audio
	TCCR3A = (3 << COM3C0) | (0b11 << WGM30); // OC3C output, fast PWM mode
	TCCR3B = (4 << CS30) | (0b11 << WGM32); // Divide by 256
	OCR3A = (FOSC / 256 / 440); // Set up 440Hz tone
	OCR3C = (FOSC / 256 / 440) / 2;
	DDRE |= 0b00100000; // Make PE5 an output

	printf_P( PSTR( "Generating video\n" ) );

	cli();
	
	// Begin showing a raster line
	do {
		// Pre-equalizing
		for( i = 6; i > 0; i-- ) {
			SYNCH(A);
			PORTF = 0b10000000;
			SYNCH(B);
			PORTF = 0b10010000;
		}
		// Field sync
		for( i = 6; i > 0; i-- ) {
			SYNCH(A);
			PORTF = 0b10000000;
			SYNCH(C);
			PORTF = 0b10010000;
		}

		// Post-equalizing
		for( i = 6; i > 0; i-- ) {
			SYNCH(A);
			PORTF = 0b10000000;
			SYNCH(B);
			PORTF = 0b10010000;
		}

		// Begin field
		for( i = 12; i > 0; i-- ) {
			SYNCF(A);
			PORTF = 0b10000000;
			SYNCF(B);
			PORTF = 0b10010000;
		}

		// Show visible
		for( i = VIDEO_LINES; i > 0; i-- ) {
			imask = i >> 4;

			SYNCF(C);

			for( j = 0; j < NUM_PIXELS; j++ )
			{
				PORTF = ((j ^ imask) & 0b00001111) | 0b10010000;
			}

			PORTF = 0b10010000;
			
			SYNCF(A);
			PORTF = 0b10000000;
			SYNCF(B);
			PORTF = 0b10010000;
		}

		// Final half field
		for( j = 0; j < NUM_PIXELS / 2; j++ )
		{
			PORTF = ((j ^ imask) & 0b00001111) | 0b10010000;
		}

		PORTF = 0b10010000;
	} while( !serial_available_f(stdin) );
	
	getchar();

	// Release external video
	DDRF = 0b00000000;
	PORTF = 0b00000000;

	// Release the peripheral clock
	DDRG &= 0b11111101;
	PORTG &= 0b11111101;

	// Release VDC reset
	DDRJ &= 0b10111111;
	PORTJ &= 0b10111111;
	
	// Silence audio
	DDRE &= 0b11011111;
	TCCR3A = 0;
	TCCR3B = 0;

	return true;
}
#undef  LINE_WIDTH
#undef  PRE_EQ_PULSE
#undef  PRE_EQ_GAP
#undef  FIELD_SYNC_PULSE
#undef  FIELD_SYNC_GAP
#undef  FRONT_PORCH
#undef  SYNC_PULSE
#undef  BACK_PORCH
#undef  NUM_PIXELS
#undef  VIDEO_LINES


bool doExtVideoTestPAL()
{
	// Disable the CV CPU
	doBusAck();
	
	// Place in external video mode
	DDRF = 0b10011111;
	PORTF = 0b10010000;

	// Hold the peripheral clock
	DDRG |= 0b00000010;
	PORTG &= 0b11111101;

	// Hold VDC reset
	DDRJ |= 0b01000000;
	PORTJ &= 0b10111111;

	int     i;
	uint8_t j;
	uint8_t imask;

	// PAL line is 64us, or 1024 clock cycles at 16MHz
	// Field-blanking lines are half-width
	#define  LINE_WIDTH 1024
	#define  PRE_EQ_PULSE 36
	#define  PRE_EQ_GAP   ((LINE_WIDTH/2) - PRE_EQ_PULSE)
	#define  FIELD_SYNC_PULSE 433
	#define  FIELD_SYNC_GAP ((LINE_WIDTH/2) - FIELD_SYNC_PULSE)
	#define  FRONT_PORCH 24
	#define  SYNC_PULSE 75
	#define  BACK_PORCH (SYNC_PULSE + 72)
	#define  NUM_PIXELS 90
	#define  VIDEO_LINES 312

	delay( 100 );
	while( serial_available_f(stdin) ) { getchar(); delay( 100 ); }

	GTCCR = 0b10000001;

	// Set up the timers for consistent frames
	TCCR1A = 0b00000000; // CTC mode, OCR1A
	TCCR1B = 0b00001001;
	TCCR1C = 0b00000000;
	OCR1A = LINE_WIDTH - 1;
	OCR1B = SYNC_PULSE - 1;
	OCR1C = BACK_PORCH - 1;
	
	TCCR4A = 0b00000000; // CTC mode, OCR4A
	TCCR4B = 0b00001001;
	TCCR4C = 0b00000000;
	OCR4A = LINE_WIDTH / 2 - 1;
	OCR4B = PRE_EQ_PULSE - 1;
	OCR4C = FIELD_SYNC_PULSE - 1;
	
	TCNT1 = 0;
	TCNT4 = 0;

	GTCCR = 0b00000000;
	
	// Also set up the timer to output audio
	TCCR3A = (3 << COM3C0) | (0b11 << WGM30); // OC3C output, fast PWM mode
	TCCR3B = (4 << CS30) | (0b11 << WGM32); // Divide by 256
	OCR3A = (FOSC / 256 / 440); // Set up 440Hz tone
	OCR3C = (FOSC / 256 / 440) / 2;
	DDRE |= 0b00100000; // Make PE5 an output

	printf_P( PSTR( "Generating video\n" ) );

	// Begin showing a raster line
	do {
		// Pre-equalizing
		for( i = 6; i > 0; i-- ) {
			SYNCH(A);
			PORTF = 0b10000000;
			SYNCH(B);
			PORTF = 0b10010000;
		}
		// Field sync
		for( i = 6; i > 0; i-- ) {
			SYNCH(A);
			PORTF = 0b10000000;
			SYNCH(C);
			PORTF = 0b10010000;
		}

		// Post-equalizing
		for( i = 6; i > 0; i-- ) {
			SYNCH(A);
			PORTF = 0b10000000;
			SYNCH(B);
			PORTF = 0b10010000;
		}

		// Begin field
		for( i = 12; i > 0; i-- ) {
			SYNCF(A);
			PORTF = 0b10000000;
			SYNCF(B);
			PORTF = 0b10010000;
		}

		// Show visible
		for( i = VIDEO_LINES; i > 0; i-- ) {
			imask = i >> 4;

			SYNCF(C);

			for( j = 0; j < NUM_PIXELS; j++ )
			{
				PORTF = ((j ^ imask) & 0b00001111) | 0b10010000;
			}

			PORTF = 0b10010000;
			
			SYNCF(A);
			PORTF = 0b10000000;
			SYNCF(B);
			PORTF = 0b10010000;
		}

		// Final half field
		for( j = 0; j < NUM_PIXELS / 2; j++ )
		{
			PORTF = ((j ^ imask) & 0b00001111) | 0b10010000;
		}

		PORTF = 0b10010000;
	} while( !serial_available_f(stdin) );
	
	getchar();

	// Release external video
	DDRF = 0b00000000;
	PORTF = 0b00000000;

	// Release the peripheral clock
	DDRG &= 0b11111101;
	PORTG &= 0b11111101;

	// Release VDC reset
	DDRJ &= 0b10111111;
	PORTJ &= 0b10111111;
	
	// Silence audio
	DDRE &= 0b11011111;
	TCCR3A = 0;
	TCCR3B = 0;

	return true;
}


static bool vdcIntTest()
{
	bool pass = true;
	
	printf_P( PSTR( "VDC Interrupt test\n" ) );

	// Set up the timers for max retrace time
	TCCR1A = 0b00000000; // Normal mode
	TCCR1B = 0b00000100; // 256 prescaler
	TCCR1C = 0b00000000;
	OCR1A = 5000;

	
	// Test VDC interrupt
	setVDCModeAll( GRAPHICS1, true, true, 0, 0);

	readVDCStatus();

	// Test NMI
	if( ( PINE & 0b00001000 ) == 0 )
	{
		printf_P( PSTR( "NMI is still active\n") );
		pass = false;
	}

	// Start the timer
	TCNT1 = 0;
	TIFR1 = 0b00000010;
	
	while( ( PINE & 0b00001000 ) != 0 )
	{
		if( ( TIFR1 & 0b00000010 ) != 0 )
		{
			printf_P( PSTR( "VDC interrupt never occurred\n" ) );
			pass = false;
			break;
		}
	}

	// Time some interrupts
	uint32_t total = 0;
	for( uint8_t i = 0; i < 10; i++ )
	{
		if( ( readVDCStatus() & 0b10000000 ) == 0 )
		{
			printf_P( PSTR( "Refresh status incorrect\n" ) );
			pass = false;
		}
		
		for( uint8_t i = 0; i < 8; i++ ) { _NOP(); }
		
		if( ( PINE & 0b00001000 ) == 0 )
		{
			printf_P( PSTR( "NMI was not reset by status read\n" ) );
			pass = false;
		}

		// Start the timer
		TCNT1 = 0;
		TIFR1 = 0b00000010;

		while( ( PINE & 0b00001000 ) != 0 )
		{
			if( ( TIFR1 & 0b00000010 ) != 0 )
			{
				printf_P( PSTR( "VDC refresh not detected\n" ) );
				pass = false;
				break;
			}
		}

		total += TCNT1;
	}
	total /= 10;
	// The time is 1/(16MHz/256) * total
	printf_P( PSTR( "Refresh frequency: %uHz\n" ), (unsigned int) ((16000000/256)/total) );

	setVDCIntEnable( false );
	
	return pass;
}

static void buildCharMap()
{
	uint8_t const *bitmap;
	uint8_t *out = common;
	uint8_t i = 0;
	do
	{
		if( ( i & 0xf ) == 0 )
		{
			bitmap = hexmap;
		}
		for( uint8_t j = 0; j<7; j++ )
		{
			*(out++) = *(bitmap++);
		}
		*(out++) = i;
	}
	while( 	(++i) != 0 );
}

static void rotateCharMap()
{
	uint8_t *cell = common;
	for( uint8_t i = 0; i < 16; i++ )
	{
		uint8_t temp[8*4];
		memcpy( temp, cell, 8 * 4 );
		memmove( cell, cell + (8 * 4), 8 * 12 );
		memcpy( cell + (8 * 12), temp, 8 * 4 );
		cell += 16 * 8;
	}
}

static void buildMCCharMap()
{
	memset( common, 0, 2048 );
	
	uint8_t const *bitmap;
	uint8_t *out = common;
	uint8_t fg = 0b00000000;
	uint8_t bg = 0b00000010;
	uint8_t i = 0;
	for( i = 0; i < 6; i++ )
	{
		if( ( i & 0x01 ) == 0 )
		{
			bitmap = hexmap;
		}

		for( uint8_t r = 0; r < 4; r++ )
		{
			for( uint8_t c = 0; c < 8; c++ )
			{
				uint8_t row1 = bitmap[0];
				uint8_t row2 = (r != 3) ? bitmap[1] : (i << 4) + c;
				for( int j = 0; j < 4; j++ )
				{
					out[j*8] = (((row1 & 0b10000000) ? fg : (bg ^ c) ) << 4) |
					((row1 & 0b01000000) ? fg : (bg ^ c) );
					out[j*8+1] = (((row2 & 0b10000000) ? fg : (bg ^ c) ) << 4) |
					((row2 & 0b01000000) ? fg : (bg ^ c) );
					row1 <<= 2;
					row2 <<= 2;
				}
				out += 32;
				bitmap += 7;
				fg = (fg & 0b00001000) | ((fg + 1) & 0b00000111);
			}
			
			bitmap -= 7 * 8 - 2;
			out -= 32 * 8 - 2;
		}

		bg++;
		fg ^= 0b00001000;

		bitmap += 7 * 8 - 8;
		out += 32 * 8 - 8;
	}
}

static void rotateMCCharMap()
{
	uint8_t *cell = common;
	for( int i = 0; i < 256 * 8; i++ )
	{
		*cell = *cell ^ 0xff;
		cell++;
	}
}

static void buildTextCharMap()
{
	uint8_t const *bitmap;
	uint8_t *out = common;
	uint8_t i = 0;
	do
	{
		if( ( i & 0xf ) == 0 )
		{
			bitmap = hexmap;
		}
		for( uint8_t j = 0; j<7; j++ )
		{
			*(out++) = *(bitmap++) << 1;
		}
		*(out++) = i;
	}
	while( 	(++i) != 0 );
}


static void writeCharMap()
{
	uint8_t const *bitmap = common;
	uint8_t i = 0;
	do
	{
		for( uint8_t j = 0; j < 8; j++ )
		{
			writeVRAM( *(bitmap++) );
		}
	}
	while( (++i) != 0 );
}

static bool vdcGraphicsITest()
{
	printf_P( PSTR( "Graphics I test:  Rotating patterns, cycling names and colors\n") );
	
	// Create the graphics tables

	for( int i = 0; i < 0x4000; i++ )
	{
		writeVRAM( 0b00000000 );
	}
	
	// Pattern tables
	// Create 4 maps of rotated characters to make sure the VDC can switch pattern bitmaps correctly
	printf_P( PSTR( "Building character map\n" ) );
	buildCharMap();
	printf_P( PSTR( "Filling pattern maps\n" ) );
	writeVRAMAddr( 0x0000 );
	writeCharMap(); // 0x0000
	rotateCharMap();
	writeCharMap(); // 0x0800
	rotateCharMap();
	writeCharMap(); // 0x1000
	rotateCharMap();
	writeCharMap(); // 0x1800
	
	// Name tables
	printf_P( PSTR( "Filling name tables\n" ) );
	writeVRAMAddr( 0x3800 );
	for( int i = 0; i < 768; i++ )
	{
		writeVRAM( (uint8_t) i );
	}
	writeVRAMAddr( 0x3c00 );
	for( int i = 767; i >= 0; i-- )
	{
		writeVRAM( (uint8_t) i );
	}

	printf_P( PSTR( "Filling color tables\n" ) );
	writeVRAMAddr( 0x2000 );
	for( uint8_t i = 0; i < 32; i++ )
	{
		writeVRAM( ( (i << 3) & 0xf0 ) | (i & 0xf) );
	}
	writeVRAMAddr( 0x2040 );
	for( uint8_t i = 31; i < 32; i-- )
	{
		writeVRAM( ( (i << 3) & 0xf0 ) | (i & 0xf) );
	}
	
	// Sprite attribute table (no sprites)
	writeVRAMAddr( 0x3b00 );
	writeVRAM( 0xd0 );
	writeVRAM( 0 );
	writeVRAM( 0 );
	writeVRAM( 0 );
	
	printf_P( PSTR( "Beginning test\n" ) );

	// Initialize graphics I
	setVDCModeAll( GRAPHICS1, true, true, 0, 0 );
	setVDCNameTableAddr( 0x3800 );
	setVDCPatternTableAddr( 0x0000 );
	setVDCColorTableAddr( 0x2000 );
	setVDCSpriteAttrTableAddr( 0x3B00 );
	setVDCSpriteTableAddr( 0x0000 );
	setVDCColors( 0, 0 );

	// Cycle through pattern tables, name tables, and color tables until user input
	uint8_t count = 0;
	uint8_t name = 0;
	uint8_t backdrop = 0;
	while( !serial_available_f( stdin ) )
	{
		switch( count++ )
		{
			case 60:
			setVDCPatternTableAddr( 0x0000 );
			count = 0;
			switch( name++ )
			{
				case 1:
				setVDCNameTableAddr( 0x3c00 );
				break;
				case 2:
				setVDCColorTableAddr( 0x2040 );
				break;
				case 3:
				setVDCNameTableAddr( 0x3800 );
				break;
				case 4:
				setVDCColorTableAddr( 0x2000 );
				name = 0;
				backdrop = (backdrop + 1) & 0x0f;
				setVDCBGColor( backdrop );
				break;
			}
			break;
			case 15:
			setVDCPatternTableAddr( 0x0800 );
			break;
			case 30:
			setVDCPatternTableAddr( 0x1000 );
			break;
			case 45:
			setVDCPatternTableAddr( 0x1800 );
			break;
		}

		// Wait for retrace
		while( ( PINE & 0b00001000 ) != 0 );
		
		readVDCStatus();
	}
	getchar();
	
	setVDCIntEnable( false );
	setVDCDisplay( false );

	return true;
}

static bool vdcGraphicsIITest()
{
	printf_P( PSTR( "Graphics II test\n") );
	
	for( int i = 0; i < 0x4000; i++ )
	{
		writeVRAM( 0b00000000 );
	}

	// Create the graphics tables
	
	// Pattern tables
	// Create 3 maps of rotated characters for the 3 segments of the screen
	printf_P( PSTR( "Building character map\n" ) );
	buildCharMap();
	printf_P( PSTR( "Filling pattern maps\n" ) );
	writeVRAMAddr( 0x0000 );
	writeCharMap(); // 0x0000
	rotateCharMap();
	writeCharMap(); // 0x0800
	rotateCharMap();
	writeCharMap(); // 0x1000
	
	// Name tables
	printf_P( PSTR( "Filling name tables\n" ) );
	writeVRAMAddr( 0x3800 );
	for( int i = 0; i < 768; i++ )
	{
		writeVRAM( (uint8_t) i );
	}
	writeVRAMAddr( 0x3c00 );
	for( int i = 767; i >= 0; i-- )
	{
		writeVRAM( (uint8_t) i );
	}

	printf_P( PSTR( "Filling color tables\n" ) );
	writeVRAMAddr( 0x2000 );
	for( int i = 0; i < 3 * 256 * 8; i++ )
	{
		writeVRAM( ( ((i >> 3) & 0xf0 ) | ( (i >> 3) & 0xf)) ^ ((i >> 10) & 0b00000110) );
	}
	
	// Sprite attribute table (no sprites)
	writeVRAMAddr( 0x3b00 );
	writeVRAM( 0xd0 );
	writeVRAM( 0 );
	writeVRAM( 0 );
	writeVRAM( 0 );
	
	printf_P( PSTR( "Beginning test\n" ) );

	// Initialize graphics II
	setVDCModeAll( GRAPHICS2, true, true, 0, 0 );
	setVDCNameTableAddr( 0x3800 );
	setVDCPatternTableAddr( 0x0000 );
	setVDCColorTableAddr( 0x2000 );
	setVDCSpriteAttrTableAddr( 0x3b00 );
	setVDCSpriteTableAddr( 0x0000 );
	setVDCColors( 0, 0 );

	// Cycle through pattern tables, name tables, and color tables until user input
	uint8_t count = 0;
	uint8_t name = 0;
	uint8_t backdrop = 0;
	while( !serial_available_f( stdin ) )
	{
		switch( count++ )
		{
			case 60:
			count = 0;
			switch( name++ )
			{
				case 1:
				setVDCNameTableAddr( 0x3c00 );
				break;
				case 2:
				break;
				case 3:
				setVDCNameTableAddr( 0x3800 );
				break;
				case 4:
				name = 0;
				backdrop = (backdrop + 1) & 0x0f;
				setVDCBGColor( backdrop );
				break;
			}
			break;
			case 15:
			break;
			case 30:
			break;
			case 45:
			break;
		}

		// Wait for retrace
		while( ( PINE & 0b00001000 ) != 0 );
		
		readVDCStatus();
	}
	getchar();
	
	setVDCIntEnable( false );
	setVDCDisplay( false );

	return true;
}

static bool vdcMulticolorTest()
{
	printf_P( PSTR( "Multicolor test:  Rotating patterns, cycling names and colors\n") );
	
	// Create the graphics tables

	for( int i = 0; i < 0x4000; i++ )
	{
		writeVRAM( 0b00000000 );
	}
	
	// Pattern tables
	// Create 2 maps of rotated characters to make sure the VDC can switch pattern bitmaps correctly
	printf_P( PSTR( "Building character map\n" ) );
	buildMCCharMap();
	printf_P( PSTR( "Filling pattern maps\n" ) );
	writeVRAMAddr( 0x0000 );
	writeCharMap();
	rotateMCCharMap();
	writeCharMap();
	
	// Name tables
	printf_P( PSTR( "Filling name tables\n" ) );
	writeVRAMAddr( 0x3800 );
	for( int i = 0; i < 768; i++ )
	{
		writeVRAM( (uint8_t) ((i & 0x1f) | ((i & 0x380) >> 2)) );
	}
	writeVRAMAddr( 0x3c00 );
	for( int i = 767; i >= 0; i-- )
	{
		writeVRAM( (uint8_t) ((i & 0x1f) | ((i & 0x380) >> 2)) ^ 0x03 );
	}
	
	// Sprite attribute table (no sprites)
	writeVRAMAddr( 0x3b00 );
	writeVRAM( 0xd0 );
	writeVRAM( 0 );
	writeVRAM( 0 );
	writeVRAM( 0 );
	
	printf_P( PSTR( "Beginning test\n" ) );

	// Initialize multicolor
	setVDCModeAll( MULTICOLOR, true, true, 0, 0 );
	setVDCNameTableAddr( 0x3800 );
	setVDCPatternTableAddr( 0x0000 );
	setVDCSpriteAttrTableAddr( 0x3B00 );
	setVDCSpriteTableAddr( 0x0000 );
	setVDCColors( 0, 0 );

	// Cycle through pattern tables, name tables, and color tables until user input
	uint8_t count = 0;
	uint8_t name = 0;
	uint8_t backdrop = 0;
	while( !serial_available_f( stdin ) )
	{
		switch( ++count )
		{
			case 60:
			setVDCPatternTableAddr( 0x0000 );
			count = 0;
			switch( name++ )
			{
				case 1:
				setVDCNameTableAddr( 0x3c00 );
				break;
				case 2:
				break;
				case 3:
				setVDCNameTableAddr( 0x3800 );
				break;
				case 4:
				name = 0;
				backdrop = (backdrop + 1) & 0x0f;
				setVDCBGColor( backdrop );
				break;
			}
			break;
			case 15:
			break;
			case 30:
			setVDCPatternTableAddr( 0x0800 );
			break;
			case 45:
			break;
		}

		// Wait for retrace
		while( ( PINE & 0b00001000 ) != 0 );
		
		readVDCStatus();
	}
	getchar();
	
	setVDCIntEnable( false );
	setVDCDisplay( false );

	return true;
}

static bool vdcTextTest()
{
	printf_P( PSTR( "Text test:  Cycling names and colors\n") );
	
	// Create the tables

	for( int i = 0; i < 0x4000; i++ )
	{
		writeVRAM( 0b00000000 );
	}
	
	// Pattern tables
	// Create 2 maps of rotated characters to make sure the VDC can switch pattern bitmaps correctly
	printf_P( PSTR( "Building character map\n" ) );
	buildTextCharMap();
	printf_P( PSTR( "Filling pattern maps\n" ) );
	writeVRAMAddr( 0x0000 );
	writeCharMap();
	rotateMCCharMap();
	writeCharMap();
	
	// Name tables
	printf_P( PSTR( "Filling name tables\n" ) );
	writeVRAMAddr( 0x3800 );
	for( int i = 0; i < 960; i++ )
	{
		writeVRAM( (uint8_t) ((i & 0x1f) | ((i & 0x380) >> 2)) );
	}
	writeVRAMAddr( 0x3c00 );
	for( int i = 959; i >= 0; i-- )
	{
		writeVRAM( (uint8_t) ((i & 0x1f) | ((i & 0x380) >> 2)) ^ 0x03 );
	}
	
	printf_P( PSTR( "Beginning test\n" ) );

	// Initialize text
	setVDCModeAll( TEXT, true, true, 0, 0 );
	setVDCNameTableAddr( 0x3800 );
	setVDCPatternTableAddr( 0x0000 );
	setVDCColors( 2, 0 );

	// Cycle through pattern tables, name tables, and color tables until user input
	uint8_t count = 0;
	uint8_t name = 0;
	uint8_t foreground = 2;
	uint8_t backdrop = 0;
	while( !serial_available_f( stdin ) )
	{
		switch( ++count )
		{
			case 60:
			setVDCPatternTableAddr( 0x0000 );
			count = 0;
			switch( name++ )
			{
				case 1:
				setVDCNameTableAddr( 0x3c00 );
				break;
				case 2:
				break;
				case 3:
				setVDCNameTableAddr( 0x3800 );
				break;
				case 4:
				name = 0;
				backdrop = (backdrop + 1) & 0x0f;
				if( backdrop == 0 )
				{
					backdrop = 2;
					foreground = (foreground + 1) & 0x0f;
					setVDCFGColor( foreground );
				}
				setVDCBGColor( backdrop );
				break;
			}
			break;
			case 15:
			break;
			case 30:
			setVDCPatternTableAddr( 0x0800 );
			break;
			case 45:
			break;
		}

		// Wait for retrace
		while( ( PINE & 0b00001000 ) != 0 );
		
		readVDCStatus();
	}
	getchar();
	
	setVDCIntEnable( false );
	setVDCDisplay( false );

	return true;
}

void write1x1sprites()
{
	
	// Sprite 0-3
	for( uint8_t i = 0; i < 32; i++ )
	{
		writeVRAM( 0b11111111 );
	}
	
	// Sprite 4
	for( uint8_t i = 0b10000000; i != 0; i >>= 1 )
	{
		writeVRAM( i );
	}
	
	// Sprite 5
	for( uint8_t i = 0b0000001; i != 0; i <<= 1 )
	{
		writeVRAM( i );
	}

	// Sprite 6
	writeVRAM( 0b11111111 );
	writeVRAM( 0b10000000 );
	writeVRAM( 0b10111111 );
	writeVRAM( 0b10100000 );
	writeVRAM( 0b10100000 );
	writeVRAM( 0b10111111 );
	writeVRAM( 0b10000000 );
	writeVRAM( 0b11111111 );
	
	// Sprite 7
	writeVRAM( 0b11100011 );
	writeVRAM( 0b00010100 );
	writeVRAM( 0b11001001 );
	writeVRAM( 0b00100110 );
	writeVRAM( 0b00110010 );
	writeVRAM( 0b11001001 );
	writeVRAM( 0b00010100 );
	writeVRAM( 0b11100011 );

	// Sprite 8
	writeVRAM( 0b11100111 );
	writeVRAM( 0b00011000 );
	writeVRAM( 0b11001011 );
	writeVRAM( 0b00100100 );
	writeVRAM( 0b01010010 );
	writeVRAM( 0b10011001 );
	writeVRAM( 0b00100100 );
	writeVRAM( 0b11000011 );

	// Sprite 9
	writeVRAM( 0b11111111 );
	writeVRAM( 0b00000001 );
	writeVRAM( 0b11111101 );
	writeVRAM( 0b00000101 );
	writeVRAM( 0b00000101 );
	writeVRAM( 0b11111101 );
	writeVRAM( 0b00000001 );
	writeVRAM( 0b11111111 );

	// Sprite 10
	writeVRAM( 0b11111111 );
	writeVRAM( 0b11111111 );
	writeVRAM( 0b11111111 );
	writeVRAM( 0b11000000 );
	writeVRAM( 0b11000000 );
	writeVRAM( 0b11000000 );
	writeVRAM( 0b11000000 );
	writeVRAM( 0b11000000 );
	
	// Sprite 11
	writeVRAM( 0b00111100 );
	writeVRAM( 0b01000010 );
	writeVRAM( 0b01000000 );
	writeVRAM( 0b00111100 );
	writeVRAM( 0b00000010 );
	writeVRAM( 0b01000010 );
	writeVRAM( 0b00111100 );
	writeVRAM( 0b00000000 );
}

void write2x2sprites()
{
	
	// Sprite 0-3
	for( uint8_t i = 0; i < 4*4*8; i++ )
	{
		writeVRAM( 0b11111111 );
	}
	
	// Sprite 4
	// Left quadrants
	writeVRAM( 0b11000000 );
	for( uint8_t i = 0b11100000; i != 0b00000000; i >>= 1 )
	{
		writeVRAM( i );
	}
	// And into right top quadrant
	for( uint8_t i = 0; i < 14; i++ )
	{
		writeVRAM( 0b00000000 );
	}
	writeVRAM( 0b10000000 );
	// Right bottom quadrant
	writeVRAM( 0b11000000 );
	for( uint8_t i = 0b11100000; i != 0b00000001; i >>= 1 )
	{
		writeVRAM( i );
	}
	
	// Sprite 5
	// Top left quadrant
	for( uint8_t i = 0; i < 7; i++ )
	{
		writeVRAM( 0b00000000 );
	}
	writeVRAM( 0b00000001 );
	// Bottom left quadrant
	writeVRAM( 0b00000011 );
	for( uint8_t i = 0b00000111; i != 0b10000000; i <<= 1 )
	{
		writeVRAM( i );
	}
	// Top right quadrant
	writeVRAM( 0b00000011 );
	for( uint8_t i = 0b00000111; i != 0b10000000; i <<= 1 )
	{
		writeVRAM( i );
	}
	// Bottom right quadrant
	writeVRAM( 0b10000000 );
	for( uint8_t i = 0; i < 7; i++ )
	{
		writeVRAM( 0b00000000 );
	}

	// Sprite 6
	// Left quadrants
	writeVRAM( 0b11111111 );
	writeVRAM( 0b10000000 );
	writeVRAM( 0b10111111 );
	for( uint8_t i = 0; i < 10; i++ )
	{
		writeVRAM( 0b10100000 );
	}
	writeVRAM( 0b10111111 );
	writeVRAM( 0b10000000 );
	writeVRAM( 0b11111111 );
	// Right quadrants
	writeVRAM( 0b11111111 );
	writeVRAM( 0b00000000 );
	writeVRAM( 0b11111111 );
	for( uint8_t i = 0; i < 10; i++ )
	{
		writeVRAM( 0b00000000 );
	}
	writeVRAM( 0b11111111 );
	writeVRAM( 0b00000000 );
	writeVRAM( 0b11111111 );

	// Sprite 7
	// Top left quadrant
	writeVRAM( 0b11100000 );
	writeVRAM( 0b00010000 );
	writeVRAM( 0b11001000 );
	for( uint8_t i = 0b00100100; i != 0b00000001; i >>= 1 )
	{
		writeVRAM( i );
	}
	// Bottom left quadrant
	writeVRAM( 0b00000101 );
	for( uint8_t i = 0b00001001; i != 0b01000000; i <<= 1 )
	{
		writeVRAM( i );
	}
	writeVRAM( 0b11000000 );
	// Top right quadrant
	writeVRAM( 0b00000111 );
	writeVRAM( 0b00001000 );
	writeVRAM( 0b00010011 );
	for( uint8_t i = 0b00100100; i != 0b00100000; i <<= 1 )
	{
		writeVRAM( i );
	}
	writeVRAM( 0b10100000 );
	// Bottom right quadrant
	writeVRAM( 0b01000000 );
	writeVRAM( 0b00100000 );
	for( uint8_t i = 0b10010000; i != 0b00000010; i >>= 1 )
	{
		writeVRAM( i );
	}
	writeVRAM( 0b00000011 );

	// Sprite 8
	// Top left quadrant
	writeVRAM( 0b11100000 );
	writeVRAM( 0b00010000 );
	writeVRAM( 0b11001000 );
	for( uint8_t i = 0b00100100; i != 0b00000001; i >>= 1 )
	{
		writeVRAM( i );
	}
	// Bottom left quadrant
	writeVRAM( 0b00000101 );
	for( uint8_t i = 0b00001001; i != 0b01000000; i <<= 1 )
	{
		writeVRAM( i );
	}
	writeVRAM( 0b11000000 );
	// Top right quadrant
	writeVRAM( 0b00000111 );
	writeVRAM( 0b00001000 );
	writeVRAM( 0b00010011 );
	for( uint8_t i = 0b00100100; i != 0b00100000; i <<= 1 )
	{
		writeVRAM( i );
	}
	writeVRAM( 0b10100000 );
	// Bottom right quadrant
	writeVRAM( 0b01000000 );
	writeVRAM( 0b00100000 );
	for( uint8_t i = 0b10010000; i != 0b00000010; i >>= 1 )
	{
		writeVRAM( i );
	}
	writeVRAM( 0b00000011 );

	// Sprite 9
	// Left quadrants
	writeVRAM( 0b11111111 );
	writeVRAM( 0b00000000 );
	writeVRAM( 0b11111111 );
	for( uint8_t i = 0; i < 10; i++ )
	{
		writeVRAM( 0b00000000 );
	}
	writeVRAM( 0b11111111 );
	writeVRAM( 0b00000000 );
	writeVRAM( 0b11111111 );
	// Right quadrants
	writeVRAM( 0b11111111 );
	writeVRAM( 0b00000001 );
	writeVRAM( 0b11111101 );
	for( uint8_t i = 0; i < 10; i++ )
	{
		writeVRAM( 0b00000101 );
	}
	writeVRAM( 0b11111101 );
	writeVRAM( 0b00000001 );
	writeVRAM( 0b11111111 );
	
	// Sprite 10
	// Left quadrants
	writeVRAM( 0b11111111 );
	writeVRAM( 0b11111111 );
	writeVRAM( 0b11111111 );
	for( uint8_t i = 0; i < 13; i++ )
	{
		writeVRAM( 0b11000000 );
	}
	// Right quadrants
	writeVRAM( 0b11111111 );
	writeVRAM( 0b11111111 );
	writeVRAM( 0b11111111 );
	for( uint8_t i = 0; i < 13; i++ )
	{
		writeVRAM( 0b00000000 );
	}

	// Sprite 11
	for( uint8_t i = 0; i < 4; i++ )
	{
		writeVRAM( 0b00111100 );
		writeVRAM( 0b01000010 );
		writeVRAM( 0b01000000 );
		writeVRAM( 0b00111100 );
		writeVRAM( 0b00000010 );
		writeVRAM( 0b01000010 );
		writeVRAM( 0b00111100 );
		writeVRAM( 0b00000000 );
	}
}

bool vdcSpriteTest( bool s2x2, bool mag )
{
	bool pass = true;
	
	printf_P( PSTR( "Testing %S with%S magnification\n" ),
	s2x2 ? PSTR( "2x2" ) : PSTR( "1x1" ),
	mag ? PSTR( "" ) : PSTR( "out" ) );

	uint8_t spacing = s2x2 ? 16 : 8;
	if( mag )
	{
		spacing *= 2;
	}
	uint8_t spriteidx = s2x2 ? 4 : 1;
	
	// Clear VRAM
	writeVRAMAddr( 0x0000 );
	for( int i = 0; i < 0x4000; i++ )
	{
		writeVRAM( 0 );
	}

	writeVRAMAddr( 0x3B00 );
	
	// Create 4 sprites that only have 1 visible pixel (0-3)
	for( uint8_t i = 0; i < 4*4; i++ )
	{
		writeVRAM( (uint8_t[]) {
			-spacing, 32-spacing+1, 0 * spriteidx, 0b10000010,
			-spacing, 255, 1 * spriteidx, 0b00000011,
			190, 32-spacing+1, 2 * spriteidx, 0b10000100,
			190, 255, 3 * spriteidx, 0b00000101,
		}[i]);
	}

	// Create a 4x4 pattern (4-19)
	uint8_t left = 1;
	uint8_t top = 0;
	for( uint8_t i = 0; i < 2; i++ )
	{
		writeVRAM( top );
		writeVRAM( left );
		writeVRAM( 4 * spriteidx );
		writeVRAM( i + 6 );
		left += spacing;

		writeVRAM( top );
		writeVRAM( left );
		writeVRAM( 4 * spriteidx );
		writeVRAM( i + 6 );
		left += spacing;

		writeVRAM( top );
		writeVRAM( left );
		writeVRAM( 5 * spriteidx );
		writeVRAM( i + 6 );
		left += spacing;

		writeVRAM( top );
		writeVRAM( left );
		writeVRAM( 5 * spriteidx );
		writeVRAM( i + 6 );

		left -= (spacing * 3);
		top += spacing;
	}
	for( uint8_t i = 0; i < 2; i++ )
	{
		writeVRAM( top );
		writeVRAM( left );
		writeVRAM( 5 * spriteidx );
		writeVRAM( i + 8 );
		left += spacing;

		writeVRAM( top );
		writeVRAM( left );
		writeVRAM( 5 * spriteidx );
		writeVRAM( i + 8 );
		left += spacing;

		writeVRAM( top );
		writeVRAM( left );
		writeVRAM( 4 * spriteidx );
		writeVRAM( i + 8 );
		left += spacing;

		writeVRAM( top );
		writeVRAM( left );
		writeVRAM( 4 * spriteidx );
		writeVRAM( i + 8 );

		left -= (spacing * 3);
		top += spacing;
		
		// In 2x2 magnified, there isn't enough room for sprite testing with all 32 sprites.  Cut off the bottom line
		if( s2x2 && mag )
		{
			break;
		}
	}

	// In 2x2 magnified, it is impossible to place all 32 sprites on the screen.
	// Skip this row for this case.
	if( !s2x2 || !mag )
	{
		// Add another row
		for( uint8_t i = 0; i < 4; i++ )
		{
			writeVRAM( top );
			writeVRAM( left );
			writeVRAM( (6 + i) * spriteidx );
			writeVRAM( 10 );
			left += spacing;
		}
		top += spacing;
		left -= 4 * spacing;
	}

	// Add 4 sprites in a row (24-27)
	top += spacing;
	for( uint8_t i = 0; i < 4; i++ )
	{
		writeVRAM( top );
		writeVRAM( left );
		writeVRAM( 10 * spriteidx );
		writeVRAM( i + 11 );
		left += spacing;
	}
	
	// Add a sentinel (28)
	writeVRAM( 0xd0 );
	writeVRAM( left );
	writeVRAM( 11 * spriteidx );
	writeVRAM( 0b00001111 );

	left--;
	
	// Add some more sprites that ought to be omitted (29-31)
	for( uint8_t i = 0; i < 3; i++ )
	{
		writeVRAM( top + i * (mag ? 6 : 3) );
		writeVRAM( left );
		writeVRAM( 10 * spriteidx );
		writeVRAM( 15 );
		// These sprites will also overlap
		left += (mag ? 4 : 2);
	}
	
	top -= spacing;
	
	// Add a permanent sentinel if necessary
	if( s2x2 && mag )
	{
		writeVRAM( 0xd0 );
		writeVRAM( left );
		writeVRAM( 11 * spriteidx );
		writeVRAM( 0b00001111 );
	}

	// Create the sprite patterns
	writeVRAMAddr( 0x0000 );

	if( s2x2 )
	{
		write2x2sprites();
	}
	else
	{
		write1x1sprites();
	}

	// Initialize graphics
	setVDCMag( mag ? 1 : 0 );
	setVDCSpriteSize( s2x2 ? 1 : 0 );
	setVDCDisplay( true );
	setVDCIntEnable( true );
	setVDCColors( 0, 0 );

	// Wait for first retrace
	while( ( PINE & 0b00001000 ) != 0 );

	readVDCStatus();

	printf_P( PSTR( "Checking sprite flags\n" ) );

	// Wait for next retrace
	while( ( PINE & 0b00001000 ) != 0 );

	uint8_t status = readVDCStatus();
	if( ( status & 0b11100000 ) != 0b10000000 )
	{
		printf_P( PSTR( "Incorrect VDC status with no truncated or coincident sprites: %02x\n" ), status );
		pass = false;
	}

	// Verified that either 1) the sentinel is working, or 2) the collision and 5th
	// sprite flags are not.  Next test:  Attempt to verify the 5th sprite flag.
	// Create a 5th sprite situation by removing the sentinel.  This will also verify
	// the sentinel.
	uint8_t sentinel_sprite = (s2x2 && mag) ? 20 : 28;

	writeVRAMAddr( sentinel_sprite * 4 + 0x3b00 );
	writeVRAM( top );

	// Wait for next retrace
	while( ( PINE & 0b00001000 ) != 0 );

	status = readVDCStatus();
	if( ( status & 0b11100000 ) != 0b11000000 ||
	( status & 0x1f ) != sentinel_sprite + 1 )
	{
		printf_P( PSTR( "Incorrect VDC status with truncated sprites: %02x\n" ), status );
		pass = false;
	}

	// Move the first of the row up to bring the next one into view
	writeVRAMAddr( (sentinel_sprite - 4) * 4 + 0x3b00 );
	writeVRAM( top );

	// Wait for next retrace
	while( ( PINE & 0b00001000 ) != 0 );

	status = readVDCStatus();
	if( ( status & 0b11100000 ) != 0b11100000 ||
	( status & 0x1f ) != sentinel_sprite + 2 )
	{
		printf_P( PSTR( "Incorrect VDC status with truncated sprites and collision: %02x\n" ), status );
		pass = false;
	}

	// Move another up
	writeVRAMAddr( (sentinel_sprite - 1) * 4 + 0x3b00 );
	writeVRAM( top );

	// Wait for next retrace
	while( ( PINE & 0b00001000 ) != 0 );

	status = readVDCStatus();
	if( ( status & 0b11100000 ) != 0b11000000 ||
	( status & 0x1f ) != sentinel_sprite + 3 )
	{
		printf_P( PSTR( "Incorrect VDC status with truncated sprites and overlap: %02x\n" ), status );
		pass = false;
	}

	// Move another up to eliminate all sprite truncation
	writeVRAMAddr( (sentinel_sprite - 2) * 4 + 0x3b00 );
	writeVRAM( top );

	// Wait for next retrace
	while( ( PINE & 0b00001000 ) != 0 );

	status = readVDCStatus();
	if( ( status & 0b11100000 ) != 0b10000000 )
	{
		printf_P( PSTR( "Incorrect VDC status with no truncated sprites and overlap: %02x\n" ), status );
		pass = false;
	}

	getchar();
	
	setVDCDisplay( false );
	setVDCIntEnable( false );

	if( pass )
	{
		printf_P( PSTR( "Sprite flag test pass\n\n" ) );
	}

	return pass;
}

bool basicVDCTest( void )
{
	bool pass = true;
	
	if( !doBusAck() )
	{
		printf_P( PSTR( "Could not test VDC\n" ) );
		return false;
	}
	
	beginIO();

	// Hold VDC reset
	DDRJ |= 0b01000000;
	PORTJ &= 0b10111111;

	for( uint8_t i = 0; i < 4; i++ ) { _NOP(); }

	// Release VDC reset
	DDRJ &= 0b10111111;
	PORTJ &= 0b10111111;
	
	if( !vdcIntTest() )
	{
		pass = false;
	}

	endIO();

	return pass;
}

bool graphicsVDCTest()
{
	printf_P( PSTR( "VDC graphics tests\n" ) );

	beginIO();

	bool pass = vdcGraphicsITest() &&
	vdcSpriteTest( false, false ) &&
	vdcSpriteTest( false, true ) &&
	vdcSpriteTest( true, false ) &&
	vdcSpriteTest( true, true ) &&
	vdcGraphicsIITest() &&
	vdcSpriteTest( false, false ) &&
	vdcSpriteTest( false, true ) &&
	vdcSpriteTest( true, false ) &&
	vdcSpriteTest( true, true ) &&
	vdcMulticolorTest() &&
	vdcSpriteTest( false, false ) &&
	vdcSpriteTest( false, true ) &&
	vdcSpriteTest( true, false ) &&
	vdcSpriteTest( true, true ) &&
	vdcTextTest();

	endIO();

	return pass;
}
